using System;
using System.Collections;
using System.Runtime.Serialization;
using System.Security.Permissions;

namespace Meta
{
	/// <summary>
	/// Summary description for GenericProcedures.
	/// </summary>
	/// 

	[Serializable]
	public class NoMatchingMethodException : ArgumentException, ISerializable {
		public readonly object Procedure;
		public readonly object[] Arguments;
		public Type[] ArgumentTypes {
			get {
				return Type.GetTypeArray(Arguments);
			}
		}

		public override string Message {
			get {
				return "No matching method could be found for the supplied arguments";
			}
		}

		public NoMatchingMethodException(object proc, object[] args) {
			Procedure = proc;
			Arguments = args;
		}

		[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter=true)]
		public override void GetObjectData(SerializationInfo info, StreamingContext context) {
			base.GetObjectData(info, context);
			info.AddValue("Procedure", Procedure);
			info.AddValue("Arguments", RemoteDataPlaceholder.PlaceholderArray(Arguments));
		}

		NoMatchingMethodException(SerializationInfo info, StreamingContext context) {
			Procedure = (Procedure)info.GetValue("Procedure", typeof(Procedure));
			Arguments = (object[])info.GetValue("Arguments", typeof(object[]));
		}
	}

	[Serializable]
	public class GenericProcedure: ApplyInterfaceProcedure
	{
		public ArrayList methodList;
		public static GenericProcedure Initialize = new GenericProcedure(Symbol.Intern("initialize"));
		public GenericProcedure(Symbol name)
		{
			this.name = name;
			methodList = new ArrayList();
		}

#if OldGenericProcedures
		public override object Apply(object[] args) {
			foreach (Method m in methodList) {
				Procedure p = m.TestArguments(args);
				if (p!=null)
					return p.Apply(args);
			}
			throw new Exception("No applicable method");
		}
#endif
		public override object Apply (object[] args) {
			return new CallNextMethod(this, 0, methodList, args).Call();
		}

		public void SetMethod(object[] argSpec, Procedure implementation) {
			Method newMethod = new Method(this, argSpec, implementation);

			foreach (IComparable m in methodList) {
				if (m.CompareTo(newMethod)==0) {
					((Method)m).Update(newMethod);
					return;
				}
			}
			methodList.Add(newMethod);
			SortMethods();
		}

		//
		// Argh.
		// I have to look up sort algorithms for partial orders because this one is cubic...
		//
		void SortMethods() {
			ArrayList newOrder = new ArrayList(methodList.Count);

			while (methodList.Count>0) {
				// Find a most specific method
				Method minimal = MostSpecificMethod();
				methodList.Remove(minimal);
				newOrder.Add(minimal);
			}
			methodList = newOrder;
		}

		public Method MostSpecificMethod() {
			for (int cindex=0; cindex<methodList.Count; cindex++) {
				IComparable candidate=(IComparable)methodList[cindex];
				if (IsMostSpecificMethod(candidate))
					return (Method)candidate;
			}
			throw new Exception("MostSpecificMethod failed");
		}

		public bool IsMostSpecificMethod(IComparable candidate) {
			for (int index=0; index<methodList.Count; index++)
				if (candidate.CompareTo(methodList[index])>0)
					return false;
			return true;
		}

		public Procedure GetMethod(Type[] arglist) {
			foreach (Method m in methodList) {
				Type[] mt = m.argTypes;
				if (mt.Length!=arglist.Length)
					goto fail;
				for (int i=0; i<arglist.Length; i++) {
					if (mt[i]!=arglist[i])
						goto fail;  // can't use continue, since it would be scoped to the for, rather than the foreach
				}
				return m.implementation;
			fail: mt=null; // Syntactically, we have to have a statement here
			}
			return null;
		}

		//
		// Basic driver class for method dispatch
		//
		[Serializable]
		class CallNextMethod : CallInterfaceProcedure {
			Procedure procedure;
			int position;
			ArrayList methodList;
			object[] arglist;

			public CallNextMethod(Procedure proc, int position, ArrayList methodList, object[] arglist) {
				this.procedure = proc;
				this.position = position;
				this.methodList = methodList;
				this.arglist = arglist;
				this.name = "CallNextMethod";
			}

			public override object Call() {
				for (int i=position; i<methodList.Count; i++) {
					Method m = (Method)methodList[i];
					Procedure p = m.TestArguments(arglist);
					if (p!=null) {
						object[] newArgs = new object[arglist.Length+1];
						newArgs[0] = new CallNextMethod(this, i+1, methodList, arglist);
						arglist.CopyTo(newArgs, 1);
						return p.Apply(newArgs);
					}
				}
				throw new NoMatchingMethodException(procedure, arglist);
			}
		}

		//
		// Holds an actual method (i.e. procedure + signature)
		//
		public class Method : IComparable{
			public Procedure generic;
			public Type[] argTypes;
			public bool restArg;
			public Procedure implementation;

			public Method(Procedure generic, Type[] argTypes, bool restArg, Procedure implementation) {
				this.generic = generic;
				this.argTypes = argTypes;
				this.restArg = restArg;
				this.implementation = implementation;
			}

			public Method(Procedure generic, object[] argSpec, Procedure implementation) {
				this.generic = generic;
				if (argSpec[argSpec.Length-1] == Symbol.sEllipsis) {
					restArg=true;
					argTypes = new Type[argSpec.Length-1];
					Array.Copy(argSpec, argTypes, argTypes.Length);
				} else {
					argTypes = new Type[argSpec.Length];
					argSpec.CopyTo(argTypes, 0);
				}
				this.implementation = implementation;
			}

			public void Update(Method newMethod) {
				this.implementation = newMethod.implementation;
				// We have to rewrite the argtypes because new versions of a type are equal to the old versions
				// but their instances don't pass IsInstanceOf
				this.argTypes = newMethod.argTypes;
			}

			static bool TestType(Type type, object arg) {
				if (type==null)
					return arg==null;
				if (type==typeof(Object))
					return true;
				return (arg==null) || type.IsInstanceOfType(arg);
			}

			public Procedure TestArguments(object[] args) {
				if (args.Length < argTypes.Length)
					return null;
				if (!restArg && args.Length>argTypes.Length) 
					return null;

				int i;
				if (restArg) {
					for (i=0; i<argTypes.Length-1; i++)
						if (!TestType(argTypes[i], args[i]))
							return null;
					for (int j=i; j<args.Length; j++)
						if (!TestType(argTypes[i], args[j]))
							return null;
				} else
					for (i=0; i<argTypes.Length; i++)
						if (!TestType(argTypes[i], args[i]))
							return null;

				return implementation;
			}

			static int TypeNestingLevel(Type t) {
				int tnl=0;
				while (t.BaseType!=null) {
					tnl++;
					t=t.BaseType;
				}
				return tnl;
			}
			int IComparable.CompareTo(object otherMethod) {
				Method other = (Method)otherMethod;

				if (restArg != other.restArg)
					return restArg?1:-1;    // rest methods are always less specific
				if (argTypes.Length!=other.argTypes.Length)
					return argTypes.Length<other.argTypes.Length?-1:1;   // always check shorter arg lists first

				// They must be equal length arglists
				for (int i=0; i<argTypes.Length; i++) {
					Type t = argTypes[i];
					Type o = other.argTypes[i];

					if (t==o)
						continue;
					if (t==null)
						return (o==null)?0:-1;
					if (o==null)
						return (t==null)?0:1;

					if (t.IsSubclassOf(o))
						return -1;
					if (o.IsSubclassOf(t))
						return 1;

					int tnl = TypeNestingLevel(t);
					int otnl = TypeNestingLevel(o);
					if (tnl>otnl)
						return -1;
					if (otnl<tnl)
						return 1;

					if (t!=o)
						// Incomparable types
						return t.FullName.CompareTo(o.FullName);
					// Same type; keep going
				}
				return 0;
			}

			public override string ToString() {
				string[] substrings = new string[2*argTypes.Length];
				
				substrings[0] = "Method "+generic.name.ToString()+" ";
				for (int i=0; i<argTypes.Length-1; i++) {
					substrings[1+2*i] = (argTypes[i]==null)?"null":argTypes[i].ToString();
					substrings[2+2*i] = ", ";
				}
				substrings[substrings.Length-1] = argTypes[argTypes.Length-1].Name.ToString();
				return String.Concat(substrings);
			}

		}
	}
}
